package Watcher;

import static java.nio.file.StandardWatchEventKind.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKind.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKind.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKind.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.HashMap;
import java.util.Vector;

/**
 * watch folders for changes
 * @author ran,barak
 *
 */
public class Watcher implements Runnable{
	private HashMap<WatchKey,Path> keyPathMap;
	private Vector<WatcherObserver> observersList;
	private WatchService watcherSvc;
	private boolean running;
	
	/**
	 * Ctor of Watcher
	 * @throws IOException
	 */
	public Watcher() throws IOException {
		// initialize members
		keyPathMap = new HashMap<WatchKey, Path>();
		watcherSvc = FileSystems.getDefault().newWatchService();
		running=false;
		observersList = new Vector<WatcherObserver>();
	}
	/**
	 * put the observer class in the list of observers
	 * @param obs the observer
	 */
	public void registerObserver(WatcherObserver obs){
		synchronized (this) {
			observersList.add(obs);
		}
	}
	/**
	 * pause the watcher
	 */
	public void pause(){
		running=false;
	}
	
	/**
	 * check if the watcher is running
	 * @return true if the watcher is running, false otherwise
	 */
	public boolean isRunning() {
		return running;
	}
	
	/**
	 * add new directory to watch
	 * @param path the path of the directory
	 * @throws IOException
	 */
	public void addDirectory(Path path) throws IOException{
		WatchKey watchKey = path.register(watcherSvc, OVERFLOW,ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
		keyPathMap.put(watchKey, path);
	}
	/**
	 * remove directory from being watch
	 * @param path the path of the directory
	 * @throws IOException
	 */
	public void removeDirectory(Path path) throws IOException{
		// create new watchService
		WatchService temp= FileSystems.getDefault().newWatchService();
		WatchKey watchKey=null;
		// search for the path and remove it
		while((watchKey = watcherSvc.poll())!=null){
			Path tempPath = keyPathMap.remove(watchKey);
			if(tempPath.compareTo(path)!=0){
				watchKey = tempPath.register(temp, OVERFLOW,ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
				keyPathMap.put(watchKey, tempPath);
			}
		}
		watcherSvc=temp;
	}
	
	/**
	 * run the watcher
	 */
	public void run() {
		running=true;
		WatchKey watchKey=null;
		Path fileName=null;
		Path path=null;
		Path lastFileName=null;
		ChangeType type=ChangeType.CREATE;
		// while the user did'nt change running state
		while (running) {        	
            try {
            	// take watch key
				watchKey=watcherSvc.take();
				System.out.println("relese from take");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// get the events
            for (WatchEvent<?> event: watchKey.pollEvents()) {
                WatchEvent<Path> watchEvent = castEvent(event);          
            	fileName=watchEvent.context();
            	path = keyPathMap.get(watchKey);
            	// check the type
            	if(watchEvent.kind()==ENTRY_CREATE)
            		type=ChangeType.CREATE;
            	else if(watchEvent.kind()==ENTRY_MODIFY)
            		type=ChangeType.MODIFY;
            	else {
            		type=ChangeType.DELETE;
            		lastFileName = watchEvent.context();
            	}
            }   
            // notify all observer
            for(WatcherObserver obs : observersList){
            	if(lastFileName!=null&&lastFileName.compareTo(fileName)!=0)
            		obs.somethingChanged(path, fileName,lastFileName, ChangeType.RENAME);
            	else
            		obs.somethingChanged(path, fileName,null, type);
            }
            watchKey.reset();
		}
	}
	
	/**
	 * cast the event to WatchEvent
	 * @param <T> the Type
	 * @param event the event
	 * @return
	 */
	private <T> WatchEvent<T> castEvent(WatchEvent<?> event) {
        return (WatchEvent<T>)event;
    }
}
